//
//  RPipeline.m
//  Foundation
//
//  Created by 吴新庭 on 2019/6/18.
//  Copyright © 2019 iyinyue. All rights reserved.
//

#import "RPipeline.h"
#import "RPipeline+Filter.h"
#import "RPipeline+Input.h"
#import <objc/runtime.h>

@interface NSObject ()

/// 产品所在的流水线
@property (nonatomic, weak) RPipeline *locatePipeline;

@end

@interface RPipeline ()

@property (nonatomic, strong) NSObject *buffer;
@property (nonatomic, strong) NSMutableArray *mArr;
@property (nonatomic, strong) NSMutableArray *holdObjects;

@property (nonatomic, strong) NSMutableArray<id<RPipelineFilterProtocol>> *filters;
@property (nonatomic, weak) id<RPipelineInputProtocol> input;

/// 列表操作同步
@property (nonatomic, strong) dispatch_semaphore_t arrOperationLock;
@property (nonatomic, strong) dispatch_semaphore_t holdOperationLock;

/// 排队同步
@property (nonatomic, strong) dispatch_semaphore_t mutex;
/// 产品数量同步控制
@property (nonatomic, strong) dispatch_semaphore_t product;

@end

@implementation RPipeline

+ (RPipeline *)pipelineWithBlock:(void (^)(RPipelineBatch * _Nonnull))action {
    RPipeline *pl = [[RPipeline alloc] init];
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (RPipelineBatch *obj in pl) {
            action(obj);
        }
#ifdef DEBUG
        NSLog(@"Pipeline End");
#endif
    });
    
    return pl;
}

- (instancetype)init {
    if (self = [super init]) {
        _batchSize = 1;
        _mArr = [NSMutableArray arrayWithObject:[NSNull null]];
        _holdObjects = [NSMutableArray array];
        _filters = [NSMutableArray array];
        _arrOperationLock = dispatch_semaphore_create(1);
        _holdOperationLock = dispatch_semaphore_create(1);
        
        _mutex = dispatch_semaphore_create(1);
        _product = dispatch_semaphore_create(0);
    }
    return self;
}

- (void)addFilter:(id<RPipelineFilterProtocol>)filter {
    NSInteger priority = 0;
    if ([filter respondsToSelector:@selector(filterPriority)]) {
        priority = filter.filterPriority;
    }
    NSInteger index = [self.filters indexOfObjectPassingTest:^BOOL(id<RPipelineFilterProtocol>  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSInteger objPriority = 0;
        if ([obj respondsToSelector:@selector(filterPriority)]) {
            objPriority = obj.filterPriority;
        }
        
        return objPriority > priority;
    }];
    if (index != NSNotFound) {
        [self.filters insertObject:filter atIndex:index];
    } else {
        [self.filters addObject:filter];
    }
}

- (void)produce:(NSObject *)object {
    if (object) {
        [self insertProduct:object atIndex:self.mArr.count - 1];
    }
}

- (void)consumed {
    dispatch_semaphore_signal(self.mutex);
}

- (void)destroy {
    self.buffer = nil;
    [self.filters removeAllObjects];
    dispatch_semaphore_signal(self.product);
    dispatch_semaphore_signal(self.mutex);  // 还原为1
}

- (void)dealloc {
    self.arrOperationLock = nil;
    self.mutex = nil;
    self.product = nil;
}

#pragma mark - Private
- (void)insertProduct:(NSObject *)object atIndex:(NSUInteger)index {
    dispatch_semaphore_wait(self.arrOperationLock, DISPATCH_TIME_FOREVER);
    [self.mArr insertObject:object atIndex:index];
    object.locatePipeline = self;
    dispatch_semaphore_signal(self.arrOperationLock);
    
    dispatch_semaphore_signal(self.product);
}

- (void)hold:(NSObject *)object {
    dispatch_semaphore_wait(self.holdOperationLock, DISPATCH_TIME_FOREVER);
    [self.holdObjects addObject:object];
    dispatch_semaphore_signal(self.holdOperationLock);
}

- (void)loose:(NSObject *)object {
    dispatch_semaphore_wait(self.holdOperationLock, DISPATCH_TIME_FOREVER);
    if ([self.holdObjects indexOfObject:object] == NSNotFound) {
        return;
    }
    
    [self.holdObjects removeObject:object];
    dispatch_semaphore_signal(self.holdOperationLock);
    
    // 解除hold后插入到最前面
    [self insertProduct:object atIndex:0];
}

#pragma mark - Enumerator
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id  _Nullable __unsafe_unretained [])buffer count:(NSUInteger)len {
    if (state->state == 0) {
        state->extra[0] = 0;
        state->mutationsPtr = &state->extra[0];
        state->state = 1;
    }
    
    NSObject *object = nil;
    do {
        // 如果没有更多产品了，并且有被动生产者，则主动取值
        if (self.mArr.count <= 1 && self.input) {
            NSObject *object = [self.input produceFor:self];
            if (object) {
                [self produce:object];
            }
        }
        
        dispatch_semaphore_wait(self.mutex, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(self.product, DISPATCH_TIME_FOREVER);
        
        NSRange range = NSMakeRange(0, MIN(self.batchSize, self.mArr.count - 1));
        if (range.length == 0) {
            return 0;
        }
        
        // 取出产品
        for (NSInteger i = 0; i < range.length - 1; i++) {
            dispatch_semaphore_wait(self.product, DISPATCH_TIME_FOREVER);
        }
        
        NSArray<NSObject *> *batchObjects = [self.mArr subarrayWithRange:range];
        [self.mArr removeObjectsInRange:range];
        
        NSMutableArray<NSObject *> *mFilteredArr = [NSMutableArray array];
        for (NSInteger i = 0; i < batchObjects.count; i++) {
            NSObject *filterObj = batchObjects[i];
            for (id<RPipelineFilterProtocol> filter in self.filters) {
                filterObj = [filter pipelineFilter:filterObj];
                
                if (!filterObj) {
                    break;
                }
            }
            if (filterObj) {
                [mFilteredArr addObject:filterObj];
            }
        }
        
        if (mFilteredArr.count == 0) {
            
        } else {
            RPipelineBatch *batch = [[RPipelineBatch alloc] init];
            batch.objects = mFilteredArr;
            object = batch;
        }
        
        self.buffer = object;
        object.locatePipeline = self;
    } while (!object);
    
    state->itemsPtr = buffer;
    *buffer = object;
    
    return 1;
}

@end

#pragma mark -
@implementation RPipelineBatch

@end

#pragma mark - 

static NSString * const kLocatePipelineKey = @"kLocatePipelineKey";
@implementation NSObject (RPipeline)

- (void)setLocatePipeline:(RPipeline *)locatePipeline {
    objc_setAssociatedObject(self, &kLocatePipelineKey, locatePipeline, OBJC_ASSOCIATION_ASSIGN);
}

- (RPipeline *)locatePipeline {
    return objc_getAssociatedObject(self, &kLocatePipelineKey);
}

- (void)consumed {
    [self.locatePipeline consumed];
}

- (void)hold {
    [self.locatePipeline hold:self];
}

- (void)loose {
    [self.locatePipeline loose:self];
}

@end
